﻿using System.Configuration;
using SeleniumToolkit.Core;
using System;

namespace SeleniumToolkit.Config
{
    /// <summary>
    /// Configuration Section for Selenium
    /// </summary>
    /// <remarks>
    /// </remarks>
    public class SeleniumConfiguration : ConfigurationSection
    {
        private static readonly ConfigurationProperty ServerHostProperty;
        private const string ServerHostKey = "server";
        private const string ServerHostDefault = "localhost";

        private static readonly ConfigurationProperty ServerPortProperty;
        private const string ServerPortKey = "port";
        private const int ServerPortDefault = 4444;

        private static readonly ConfigurationProperty DefaultBrowserProperty;
        private const string DefaultBrowserKey = "defaultBrowser";
        private const string DefaultBrowserDefault = "*iexplore";

        private static readonly ConfigurationProperty BrowserUrlProperty;
        private const string BrowserUrlKey = "BrowserUrl";
        private const string BrowserUrlDefault = "http://www.google.com";

        private static readonly ConfigurationProperty TimeoutProperty;
        private const string TimeoutKey = "timeout";
        private const int TimeoutDefault = 90000;

        private static readonly ConfigurationProperty FactoryTypeProperty;
        private const string FactoryTypeKey = "factoryType";

        private static readonly ConfigurationPropertyCollection PropertyCollection;

        /// <summary>
        /// Static constructor sets up configuration properties
        /// </summary>
        static SeleniumConfiguration()
        {
            PropertyCollection = new ConfigurationPropertyCollection();

            // attributes of configuration
            ServerHostProperty = new ConfigurationProperty(ServerHostKey, typeof(string), ServerHostDefault);
            ServerPortProperty = new ConfigurationProperty(ServerPortKey, typeof(int), ServerPortDefault);
            DefaultBrowserProperty = new ConfigurationProperty(DefaultBrowserKey, typeof(string), DefaultBrowserDefault);
            BrowserUrlProperty = new ConfigurationProperty(BrowserUrlKey, typeof(string), BrowserUrlDefault);
            TimeoutProperty = new ConfigurationProperty(TimeoutKey, typeof(int), TimeoutDefault);
            FactoryTypeProperty = new ConfigurationProperty(FactoryTypeKey, typeof(string), null);

            PropertyCollection.Add(ServerHostProperty);
            PropertyCollection.Add(ServerPortProperty);
            PropertyCollection.Add(DefaultBrowserProperty);
            PropertyCollection.Add(BrowserUrlProperty);
            PropertyCollection.Add(TimeoutProperty);
            PropertyCollection.Add(FactoryTypeProperty);

            // nested elements
            AddinSettingsProperty = new ConfigurationProperty(AddinSettingsKey, typeof(SeleniumConfigurationAddinElement));
            RuntimeSettingsProperty = new ConfigurationProperty(RuntimeSettingsKey, typeof(SeleniumConfigurationRuntimeElement));
            UserExtensionsProperty = new ConfigurationProperty(UserExtensionsKey, typeof(UserExtensionElementCollection));
            BrowserMapProperty = new ConfigurationProperty(BrowserCollectionKey, typeof(BrowserMapElementCollection));

            PropertyCollection.Add(AddinSettingsProperty);
            PropertyCollection.Add(RuntimeSettingsProperty);
            PropertyCollection.Add(UserExtensionsProperty);
            PropertyCollection.Add(BrowserMapProperty);

        }

        #region Constructor
        /// <summary>
        /// Default Constructor
        /// </summary>
        public SeleniumConfiguration()
        {
            AddinSettings = new SeleniumConfigurationAddinElement();
            RuntimeSettings = new SeleniumConfigurationRuntimeElement();
            UserExtensions = new UserExtensionElementCollection();
            Browsers = new BrowserMapElementCollection();
        } 
        #endregion

        /// <summary>
        /// Gets the Factory Provider used for constructing Selenium instances
        /// </summary>
        /// <returns>Returns a Factory Provider used to create custom selenium instances</returns>
        public virtual ISeleniumFactoryProvider ResolveFactory()
        {
            if (FactoryType == null)
            {
                return new DefaultSeleniumFactory();
            }

            Type type = Type.GetType(FactoryType, true, true);
// ReSharper disable AssignNullToNotNullAttribute
            var instance = Activator.CreateInstance(type) as ISeleniumFactoryProvider;
// ReSharper restore AssignNullToNotNullAttribute
            if (instance == null)
            {
                throw new ConfigurationErrorsException("The factory type supplied does not implement the ISeleniumFactoryProvider interface.");
            }
            return instance;
        }

        /// <summary>
        /// Configuration Properties
        /// </summary>
        /// <remarks>
        /// Contains the definition of the custom properties for the configuration
        /// </remarks>
        protected override ConfigurationPropertyCollection Properties
        {
            get
            {
                return PropertyCollection;
            }
        }

        #region Configuration Attributes

        /// <summary>
        /// The default browser string for the selenium remote control (default is *iexplore)
        /// </summary>
        [ConfigurationProperty(DefaultBrowserKey, DefaultValue = DefaultBrowserDefault)]
        public string DefaultBrowser
        {
            get { return (string)base[DefaultBrowserProperty]; }
            set { base[DefaultBrowserProperty] = value; }
        }

        /// <summary>
        /// The base url for the selenium remote control
        /// </summary>
        [ConfigurationProperty(BrowserUrlKey, DefaultValue = BrowserUrlDefault)]
        public string BrowserUrl
        {
            get { return (string)base[BrowserUrlProperty]; }
            set { base[BrowserUrlProperty] = value; }
        }

        /// <summary>
        /// The server name of the selenium remote control or grid (default is localhost)
        /// </summary>
        [ConfigurationProperty(ServerHostKey, DefaultValue = ServerHostDefault)]
        public string SeleniumServer
        {
            get { return (string)base[ServerHostProperty]; }
            set { base[ServerHostProperty] = value; }
        }

        /// <summary>
        /// The port for the selenium remote control or grid (default is 4444)
        /// </summary>
        [ConfigurationProperty(ServerPortKey, DefaultValue = ServerPortDefault)]
        public int SeleniumServerPort
        {
            get { return (int)base[ServerPortProperty]; }
            set { base[ServerPortProperty] = value; }
        }

        /// <summary>
        /// Defines the default timeout value for each request
        /// </summary>
        [ConfigurationProperty(TimeoutKey, DefaultValue = TimeoutDefault)]
        public int Timeout
        {
            get { return (int)base[TimeoutProperty]; }
            set { base[TimeoutProperty] = value; }
        }

        /// <summary>
        /// Defines the fully qualified type of the ISeleniumFactoryProvider
        /// </summary>
        [ConfigurationProperty(FactoryTypeKey)]
        public string FactoryType
        {
            get { return (string)base[FactoryTypeProperty]; }
            set { base[FactoryTypeProperty] = value; }
        }
        #endregion

        #region Nested Configuration Elements

        /// <summary>
        /// Configuration Settings
        /// </summary>
        [ConfigurationProperty(AddinSettingsKey)]
        public SeleniumConfigurationAddinElement AddinSettings
        {
            get { return (SeleniumConfigurationAddinElement)base[AddinSettingsProperty]; }
            set { base[AddinSettingsProperty] = value; }
        }

        /// <summary>
        /// Settings used to control the Selenium RC Process
        /// </summary>
        [ConfigurationProperty(RuntimeSettingsKey)]
        public SeleniumConfigurationRuntimeElement RuntimeSettings
        {
            get { return (SeleniumConfigurationRuntimeElement)base[RuntimeSettingsProperty]; }
            set { base[RuntimeSettingsProperty] = value; }
        }

        /// <summary>
        /// Settings for user-defined javascript extensions
        /// </summary>
        [ConfigurationProperty(UserExtensionsKey, IsRequired=false, IsDefaultCollection=false)]
        [ConfigurationCollection(typeof(UserExtensionElement),AddItemName="add", ClearItemsName="clear", RemoveItemName="remove", CollectionType=ConfigurationElementCollectionType.AddRemoveClearMap)]
        public UserExtensionElementCollection UserExtensions
        {
            get { return (UserExtensionElementCollection)base[UserExtensionsProperty]; }
            set { base[UserExtensionsProperty] = value; }
        }

        /// <summary>
        /// Browser Map configuration
        /// </summary>
        [ConfigurationProperty(BrowserCollectionKey, IsRequired = false, IsDefaultCollection = false)]
        [ConfigurationCollection(typeof(BrowserMapElement), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove", CollectionType = ConfigurationElementCollectionType.AddRemoveClearMap)]
        public BrowserMapElementCollection Browsers
        {
            get { return (BrowserMapElementCollection)base[BrowserMapProperty]; }
            set { base[BrowserMapProperty] = value; }
        }

        private static readonly ConfigurationProperty AddinSettingsProperty;
        private const string AddinSettingsKey = "addin";

        private static readonly ConfigurationProperty RuntimeSettingsProperty;
        private const string RuntimeSettingsKey = "runtime";

        private static readonly ConfigurationProperty UserExtensionsProperty;
        private const string UserExtensionsKey = "userExtensions";

        private static readonly ConfigurationProperty BrowserMapProperty;
        private const string BrowserCollectionKey = "browsers";
        #endregion

        /// <summary>
        /// Gets the active Selenium configuration from the local configuration settings.  Null if not found.  Errors are not caught
        /// </summary>
        /// <returns></returns>
        public static SeleniumConfiguration GetConfig()
        {
            var config = ConfigurationManager.GetSection("Selenium") as SeleniumConfiguration;
            if (config == null)
                throw new MissingSeleniumConfigurationException("Missing \"Selenium\" configuration element.");

            return config;
        }

        /// <summary>
        /// Gets the active Selenium configuration from the local configuration settings.
        /// </summary>
        /// <param name="supressErrors">flag to determine if errors should be thrown</param>
        /// <returns></returns>
        public static SeleniumConfiguration GetConfig(bool supressErrors)
        {
            SeleniumConfiguration config = null;

            try
            {
                config = GetConfig();
            }
            catch
            {
                if (!supressErrors)
                    throw;
            }

            return config;
        }
    }
}
